package com.heima.wemedia.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.heima.apis.article.IArticleClient;
import com.heima.common.exception.CustomException;
import com.heima.common.tess4j.Tess4jClient;
import com.heima.file.service.FileStorageService;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.wemedia.pojos.WmChannel;
import com.heima.model.wemedia.pojos.WmNews;
import com.heima.model.wemedia.pojos.WmSensitive;
import com.heima.model.wemedia.pojos.WmUser;
import com.heima.utils.common.SensitiveWordUtil;
import com.heima.wemedia.mapper.WmChannelMapper;
import com.heima.wemedia.mapper.WmNewsMapper;
import com.heima.wemedia.mapper.WmSensitiveMapper;
import com.heima.wemedia.mapper.WmUserMapper;
import com.heima.wemedia.service.WmAutoScanService;
import com.heima.wemedia.service.WmGreenScanService;
import net.sourceforge.tess4j.TesseractException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author 陈辉
 * @data 2023 16:24
 */
@Service
@SuppressWarnings("all")
public class WmAutoScanServiceImpl implements WmAutoScanService {
    @Autowired
    private WmNewsMapper wmNewsMapper;
    @Autowired
    private WmGreenScanService wmGreenScanService;
    @Autowired
    private IArticleClient articleClient;
    @Autowired
    private WmUserMapper wmUserMapper;
    @Autowired
    private WmChannelMapper wmChannelMapper;
    @Autowired
    private WmSensitiveMapper wmSensitiveMapper;
    @Autowired
    private Tess4jClient tess4jClient;
    @Autowired
    private FileStorageService fileStorageService;

    /**
     * 自媒体文章自动审核功能
     *
     * @param id
     */
    @Override
//    @Async          //方法异步声明： 未来一旦调用该方法，spring底层会从线程池取一个线程来单独执行该方法
    public void autoScan(Integer id) {
        //1. 校验文章是否存在
        WmNews wmNews = wmNewsMapper.selectById(id);
        if (wmNews == null) {
            throw new CustomException(AppHttpCodeEnum.DATA_NOT_EXIST);
        }

        //2. 文章存在：开启审核流程: 先审文本，再审图片
        //2.1 提取出文章中的文本和图片
        Map<String, Object> map = handleTextAndImage(wmNews);

        //3. 审核文字
        boolean isTextScan = scanText((String) map.get("text"), wmNews);
        if (!isTextScan) {
            return;
        }
        //4. 审核图片
        boolean isImageScan = scanImage((ArrayList<String>) map.get("image"), wmNews);
        if (!isImageScan) {
            return;
        }

        //5. 远程调用article微服务，进行app文章的数据同步
        saveArticle(wmNews);

    }

    /**
     * 将自媒体文章同步到app端
     *
     * @param wmNews
     */
    private void saveArticle(WmNews wmNews) {
        ArticleDto dto = new ArticleDto();
        BeanUtils.copyProperties(wmNews, dto);

        //1. 针对不能直接拷贝的字段进行单独处理
        //1.1 作者id
        dto.setAuthorId(Long.valueOf(wmNews.getUserId()));
        //1.2 作者名称
        WmUser wmUser = wmUserMapper.selectById(wmNews.getUserId());
        if (wmUser != null) {
            dto.setAuthorName(wmUser.getName());
        }
        //1.3 布局
        dto.setLayout(wmNews.getType());
        //1.4 频道名称
        WmChannel wmChannel = wmChannelMapper.selectById(wmNews.getChannelId());
        if (wmChannel != null) {
            dto.setChannelName(wmChannel.getName());
        }

        //2. 发送feign远程调用，调用article微服务进行app文章保存
        ResponseResult responseResult = articleClient.saveArticle(dto);
        if (responseResult.getCode().equals(200)) {
            Long articleId = (Long) responseResult.getData();
            wmNews.setArticleId(articleId);
            //修改文章状态：9（已发布）
            updateWmNews(wmNews, WmNews.Status.PUBLISHED, "审核成功!");
        }
    }

    /**
     * 图片审核
     *
     * @param imageList
     * @param wmNews
     * @return
     */
    private boolean scanImage(ArrayList<String> imageList, WmNews wmNews) {
        //假定图片审核没问题
        boolean res = true;

        try {
            //如果没有图片，那就直接返回res
            if (imageList == null || imageList.size() == 0)
                return res;

            //有图片：审核图片
            for (String imgUrl : imageList) {
                //先内审  -- tess4j  ocr 【提取图片中的文字进行审核】
                byte[] bytes = fileStorageService.downLoadFile(imgUrl);
                ByteArrayInputStream in = new ByteArrayInputStream(bytes);
                BufferedImage bufferedImage = ImageIO.read(in);
                String textInImage = tess4jClient.doOCR(bufferedImage);
                //对图片中的文字进行审核
                boolean flag = scanText(textInImage, wmNews);
                if (!flag){
                    return flag;
                }

                //再外审 -- 对接第三方api 【百度云】
                Map<String, String> resultMap = wmGreenScanService.scanImage(bytes);
                String conclusionType = resultMap.get("conclusionType");
                if ("2".equals(conclusionType)) {
                    updateWmNews(wmNews, WmNews.Status.FAIL, "审核失败: 图片内容违规!");
                    return false;
                } else if ("3".equals(conclusionType) || "4".equals(conclusionType)) {
                    //百度云图片审核疑似，直接修改文章状态：
                    updateWmNews(wmNews, WmNews.Status.ADMIN_AUTH, "待人工审核: 图片内容疑似违规!");
                    return false;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return res;
    }


    /**
     * 文本内容审核
     *
     * @param text
     * @return
     */
    private boolean scanText(String text, WmNews wmNews) {
        //先走系统内部的敏感词过滤
        boolean isSensitive = handleSensitiveWord(text, wmNews);
        if (!isSensitive)
            return isSensitive;

        //再走外部的敏感词过滤
        Map<String, String> resultMap = wmGreenScanService.scanText(text);
        String conclusionType = resultMap.get("conclusionType");
        if ("1".equals(conclusionType)) {
            //文本审核通过
            return true;
        } else if ("2".equals(conclusionType)) {
            updateWmNews(wmNews, WmNews.Status.FAIL, "审核失败: 文本内容违规!");
            return false;
        } else {
            //百度云文本审核疑似，直接修改文章状态：
            updateWmNews(wmNews, WmNews.Status.ADMIN_AUTH, "待人工审核: 文本内容疑似违规!");
            return false;
        }
    }

    /**
     * 自定义敏感词过滤
     * @param text   文章的文本内容
     * @param wmNews
     * @return
     */
    private boolean handleSensitiveWord(String text, WmNews wmNews) {
        //1. 查询数据库表：wm_sensitive，得到所有的自管理敏感词
        List<String> list = wmSensitiveMapper.selectList(null)
                .stream().map(wmSensitive -> wmSensitive.getSensitives())
                .collect(Collectors.toList());

        //2. 初始化DFA算法的敏感词库
        SensitiveWordUtil.initMap(list);

        //3. 敏感词匹配
        Map<String, Integer> map = SensitiveWordUtil.matchWords(text);

        //4. 处理结果
        if (map == null || map.size() == 0) {
            //没有敏感词，返回true
            return true;
        } else {
            //修改文章状态：2（审核失败）
            updateWmNews(wmNews, WmNews.Status.FAIL, "审核失败,文本内容违规：" + map);
            return false;
        }

    }

    /**
     * 封装一个专门用来修改文章状态的方法
     *
     * @param wmNews
     * @param status
     * @param reason
     */
    private void updateWmNews(WmNews wmNews, WmNews.Status status, String reason) {
        wmNews.setStatus(status.getCode());
        wmNews.setReason(reason);
        wmNewsMapper.updateById(wmNews);
    }

    /**
     * 提取出文章中的文本和图片
     *
     * @param wmNews
     * @return
     */
    private Map<String, Object> handleTextAndImage(WmNews wmNews) {
        //先拿到文章内容
        String content = wmNews.getContent();
        JSONArray jsonArray = JSONArray.parseArray(content);

        //设计一个StringBuilder容器用来存放所有的文本数据
        StringBuilder sb = new StringBuilder(wmNews.getTitle());
        //设计一个ArrayList容器用来存放所有的图片数据
        ArrayList<String> list = new ArrayList<>();

        for (Object obj : jsonArray) {
            JSONObject json = (JSONObject) obj;

            if ("text".equals(json.getString("type"))) {
                sb.append(json.getString("value"));
            } else if ("image".equals(json.getString("type"))) {
                list.add(json.getString("value"));
            }
        }
        //封装map返回
        Map<String, Object> map = new HashMap<>();
        map.put("text", sb.toString());
        map.put("image", list);

        return map;
    }
}
